//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<XTagLayout.cpp>
///	@path	~/src/gui/xgui/
///	@date	2008/06/29
///	@desc	.

#define _FULLAPI
#include "config/config.h"

#include "gui/xgui/XTagLayout.h"

namespace xeres {

	IMPL_IOBJECT_CLASS_DYNAMIC( XTagLayout );

	// XTagLayout
	XTagLayout::XTagLayout( void )
		: IGuiControl( Rect<int>() , true , true , true )
		, m_font( StyleFont::CreateFont() )
		, m_uni( NULL )
	{

	}
	// XTagLayout
	XTagLayout::XTagLayout( RefWeak<StyleFont> font , ImageResource * imageRes , const Rect<int>& placement )
		: IGuiControl( placement , true , true , true )
		, m_font( font )
		, m_rect( placement )
		, m_imageRes( imageRes )
		, m_uni( NULL )
	{
		m_rect = GetRect();
		m_rect = make_rect( m_rect.left() + 2 , m_rect.top() + 2 , m_rect.width() - 4 , m_rect.height() - 4 );
	}
	// ~XTagLayout
	XTagLayout::~XTagLayout( void )
	{
		Clear();
	}
	// SetSize
	bool XTagLayout::SetSize( const Pair<int>& size )
	{
		if( __super::SetSize( size ) )
		{
			m_rect = GetRect();
			m_rect = make_rect( m_rect.left() + 2 , m_rect.top() + 2 , m_rect.width() - 4 , m_rect.height() - 4 );
			return true;
		}
		return false;
	}
	// SetPlacement
	bool XTagLayout::SetPlacement( const Rect<int>& placement )
	{
		if( __super::SetPlacement( placement ) )
		{
			m_rect = GetRect();
			m_rect = make_rect( m_rect.left() + 2 , m_rect.top() + 2 , m_rect.width() - 4 , m_rect.height() - 4 );
			return true;
		}
		return false;
	}
	// SetParent
	bool XTagLayout::SetParent( RefWeak<IGuiForm> parent )
	{
		if( __super::SetParent( parent ) )
		{
			if( !m_tickTag.empty() && m_parent.IsValid() )
			{
				m_parent->AddTickable( this );
			}
			return true;
		}
		return false;
	}
	// OnKeyInput
	HandleInput XTagLayout::OnKeyInput( Updater& updater )
	{
		return INPUT_UNHANDLED;
	}
	// OnMouseInput
	HandleInput XTagLayout::OnMouseInput( Updater& updater )
	{
		return INPUT_UNHANDLED;
	}
	// OnFocusIn
	void XTagLayout::OnFocusIn( Updater& updater , RefWeak<IGuiComp> last )
	{
		__super::OnFocusIn( updater , last );
		if( m_focus )
		{
// 			m_bActive = true;
// 			ResetCaretBlink( true );
// 			// set flashing
// 			m_caretFlash.Set( 0 );
// 			m_caretFlash.SetDest( 255 );
// 			m_parent->AddTickable( this );
		}
	}
	// OnFocusOut
	void XTagLayout::OnFocusOut( Updater& updater , RefWeak<IGuiComp> next )
	{
		__super::OnFocusOut( updater , next );
		if( !m_focus )
		{
// 			m_bActive = false;
// 			// clear caret flashing
// 			m_caretFlash.Set( 0 , false );
		}
	}
	// Tick
	void XTagLayout::Tick( Updater& updater , float dTime )
	{
		Rect<int> region;
		for( std::set< Tag * >::iterator it = m_tickTag.begin() ;
			it != m_tickTag.end() ; ++it )
		{
			(*it)->Tick( updater , dTime , region );
		}
		if( !region.is_empty() )
		{
			region.offset( GetPosition() );
			UpdateControl();
		}
	}
	// Draw
	void XTagLayout::Draw( Composition& composition )
	{
		Rect<int> bound = make_rect( 0 , 0 , m_rect.width() , m_rect.height() );

		//composition.graphics().DrawRectangle( &Gdiplus::Pen( Gdiplus::Color(255,255,255) ) , toRect(bound) );

		for( size_t i = 0 , total = m_lines.size() ; i < total ; ++i )
		{
			const Line& l = m_lines[i];
			Rect<int> region = make_rect<int>( 0 , l.m_base , l.m_height , l.m_width );
			
			if( composition.graphics().IsVisible( toRect(region) ) )
				m_lines[i].Draw( composition , bound );
		}
	}
	// Clear
	void XTagLayout::Clear( void )
	{
		// Line by line clear tags.
		for( size_t i = 0 , total = m_lines.size() ; i < total ; ++i )
		{
			Tag * tag = m_lines[i].m_leading;
			while( tag )
			{
				Tag * next = tag->m_next;
				// Clean up tag info.
				ReleaseTag( tag );
				// Free tag to object pool.
				FreeTag( tag );
				tag = next;
			}
		}
		m_lines.clear();
	}
	// SetText
	void XTagLayout::SetText( const WString& text )
	{
		Clear();
		m_buffer = text;
		
		// Relayout all texts.
		Relayout( 0 );
	}
	// Relayout
	void XTagLayout::Relayout( int line /*= 0 */ )
	{
		// Relayout all items.

		// Calculate focusing size.
		Rect<int> place = GetBound();
		int x = m_rect.left() - place.left();
		int y = m_rect.top() - place.top();
		size_t width = m_rect.width();
		size_t height = m_rect.height();

		TextStyle style;
		style.m_font = m_font;
		
		const wchar_t * buffer = m_buffer.c_str();

		Pair<int> pos( x , y );
		MakeLines( buffer , pos , width , height , style );
	}

	// MakeLines
	void XTagLayout::MakeLines( const wchar_t *& buffer , const Pair<int>& pos , size_t width , size_t height , TextStyle& style )
	{
		size_t space = width;

		WString text;
		Pair<int> cursor( pos );

		// Current line.
		Line line;
		line.NewLine();

		// Tag by tag.
		for(;;)
		{
			if( buffer[0] == '\r' )
			{
				// skip
				++buffer;
				continue;
			}

			// Special type.
			if( buffer[0] == '#' )
			{
				if( buffer[1] != '&' )
				{
					// A tag or style.
					Tag * tag = NULL;
					TextStyle saved( style );

					if( !text.empty() )
					{
						FlushTexts( line , text , cursor , space , saved );
						text.clear();
					}

					switch( ParseTag( tag , buffer , cursor , width , style ) )
					{
					case TAG_IMAGE:
						// Add image to line.
						if( tag->GetWidth() > ( space - ( line.m_width ) ) && !line.IsEmpty() )
						{
							AppendLine( line , cursor );
							line.NewLine();
						}
						break;
					case TAG_NEWLINE:
						// Flush current text.
						{
							FlushTexts( line , text , cursor , space , style );
							if( !line.IsEmpty() )
							{
								AppendLine( line , cursor );
								line.NewLine();
							}
							text.clear();
						}
						break;
					case TAG_STYLE:
						// Just change style
						break;
					case TAG_NONE:
						continue;
					}
					if( tag )
					{
						line.AppendTag( tag , cursor );
						CreatedTag( tag );
					}
				}
				else if( buffer[1] == '&' )
				{
					// Translate '#&' to #
					text.push_back( '#' );
					buffer += 2;
				}
				else if( buffer[1] == '\0' )
				{
					TRACE_WARNING( _S("XTagLayout::MakeLines: Missing tag after '#'.") );
					++buffer;
				}
				else
				{
					TRACE_WARNING( _S("XTagLayout::MakeLines: Invalid tag of '#%c'.") , buffer[1] );
					buffer += 2;
				}
			}
			else
			{
				// Regular type
				if( *buffer == '\0' )
				{
					FlushTexts( line , text , cursor , space , style );
					if( !line.IsEmpty() )
						AppendLine( line , cursor );
					return;
				}
				
				// Add to text.
				text.push_back( *buffer );

				if( *buffer == '\n' )
				{
					FlushTexts( line , text , cursor , space , style );
					if( !line.IsEmpty() )
					{
						AppendLine( line , cursor );
						line.NewLine();
					}
					text.clear();
				}

				++buffer;
			}
		}
	}
	// FlushTexts
	void XTagLayout::FlushTexts( Line& line , const WString& buffer , Pair<int>& pos , size_t space , TextStyle& style )
	{
		// Rest space for text.
		int rest = (int)space - pos.x;
		size_t height = line.m_height;

		// Finish current text or buffer.
		int count = (int)buffer.size();
		int start = 0;
		while( count > start )
		{
			Tag * tag = NULL;
			TagResult result = MakeText( tag , buffer , start , rest , style );

			if( tag )
				tag->m_start = pos.x;

			if( result == TAG_NEWLINE )
			{
				if( tag )
				{
					line.AppendTag( tag , pos );
					CreatedTag( tag );
				}

				AppendLine( line , pos );
				// Update context.
				rest = space;
				// New line.
				line.NewLine();
				height = 0;
			}
			else if( result == TAG_TEXT )
			{
				line.AppendTag( tag , pos );
				CreatedTag( tag );

				rest -= tag->GetWidth();
			}
			else if( result == TAG_NONE )
			{
				incompleted;
			}
		}
	}
	namespace
	{
		inline bool __isalpha( wchar_t ch )
		{
			if( ch < 255 )
				return iswalpha( ch ) != 0;
			else
				return false;
		}

		inline bool __isdigit( wchar_t ch )
		{
			if( ch < 255 )
				return iswdigit( ch ) != 0;
			else
				return false;
		}
	}
	// MakeText
	XTagLayout::TagResult XTagLayout::MakeText( Tag *& tag , const WString& text , int& start , size_t width , TextStyle& style )
	{
		XS_ASSERT( tag == NULL );

		// Analysis text.
		int count = - 1;
		int nTrail = -1;

		m_uni.SetFont( style.m_font->hfont() );
		m_uni.SetText( text.c_str() + start );
		XS_VERIFY( m_uni.XtoCP( (int)width - 2 , &count , &nTrail ) );
		m_uni.Clear();

		if( !nTrail )
			count -= 1;
		if( count == 0 )
			return TAG_NEWLINE;
		if( count < 0 )
			return TAG_NONE;

		int skipping = 0;

		// Word grouping.
		{
			while( count > 0 && __isalpha( text[ start + count ] ) && __isalpha( text[ start + count - 1 ] ) )
			{
				--count;
				++skipping;
				if( skipping > 5 )
				{
					count += skipping;
					skipping = 0;
					break;
				}
			}

			if( count <= 1 && skipping > 0 )
				return TAG_NEWLINE;
		}
		// Number grouping.
		{
			while( count > 0 && __isdigit( text[ start + count ] ) && __isdigit( text[ start + count - 1 ] ) )
			{
				--count;
				++skipping;
				if( skipping > 8 )
				{
					count += skipping;
					skipping = 0;
					break;
				}
			}

			if( count <= 1 && skipping > 0 )
				return TAG_NEWLINE;
		}

		tag = AllocTag();
		tag->m_style = style;
		tag->SetText( text.substr( start , count ) );

		// Update start position.
		start += count;

		if( skipping > 0 || start < (int)text.size() )
			return TAG_NEWLINE;
		else
			return TAG_TEXT;
	}
	// ParseTag
	XTagLayout::TagResult XTagLayout::ParseTag( Tag *& tag , const wchar_t *& buffer , const Pair<int>& pos , size_t width , TextStyle& style )
	{
		TagResult result = TAG_NONE;
		for(;;)
		{
			// See what type of tag next.
			if( buffer[0] == '#' && buffer[1] != '&' )
			{
				// A modifying tag.
				wchar_t modifier = buffer[1];
				buffer += 2;
				switch( modifier )
				{
				case '!':
					// Should be form like	#!			, clear all styles.
					{
						style.SetDefault();
						style.m_font = m_font;
						result = TAG_STYLE;
						continue;
					}

				case 'X':
				case 'x':
					// Should be form like	#x00ff00  or  #X00ff00 , a color modifier.
					{
						bool failed = false;
						uint32 value = 0;
						for( int i = 0 ; i < 6 ; ++i , ++buffer )
						{
							//
							if( buffer[0] == '\0' )
							{
								TRACE_WARNING( _S("XTagLayout::ParseTag: '#x' tag corrupted with unexpected end of text.") );
								return TAG_NONE;
							}

							if( ( buffer[0] == '\n' ) || ( buffer[0] == '\r' ) )
							{
								TRACE_WARNING( _S("XTagLayout::ParseTag: '#x' tag corrupted with unexpected end of line.") );
								return TAG_NONE;
							}

							int v = _xatoc( buffer[0] );
							if( v == -1 )
							{
								TRACE_WARNING( _S("XTagLayout::ParseTag: '#x' tag corrupted with unexpected color value.") );
								failed = true;
								break;
							}
							value = value << 4;
							value |= v;
						}
						if( failed )
							continue;

						value |= 0xff000000;

						if( modifier == 'x' )
							style.m_textColor = ARGB( value );
						else
							style.m_shadowColor = ARGB( value );

						continue;
					}
					break;

				case 'r':
					// Should be form like	#r			, next line.
					{
						return TAG_NEWLINE;
					}

					// Should be form like		#C		, a color modifier.
				case 'R': // Red
					{
						style.m_textColor = ARGB( 0xffff0000 );
						result = TAG_STYLE;
						continue;
					}
				case 'G': // Green
					{
						style.m_textColor = ARGB( 0xff00ff00 );
						result = TAG_STYLE;
						continue;
					}
				case 'B': // Blue
					{
						style.m_textColor = ARGB( 0xff0000ff );
						result = TAG_STYLE;
						continue;
					}
				case 'Y': // Yellow
					{
						style.m_textColor = ARGB( 0xffffff00 );
						result = TAG_STYLE;
						continue;
					}
				case 'W': // White
					{
						style.m_textColor = ARGB( 0xffffffff );
						result = TAG_STYLE;
						continue;
					}
				case 'K': // Black
					{
						style.m_textColor = ARGB( 0xff000000 );
						result = TAG_STYLE;
						continue;
					}
				case 'C': // Cyan
					{
						style.m_textColor = ARGB( 0xff00ffff );
						result = TAG_STYLE;
						continue;
					}
				case 'P': // Purple
					{
						style.m_textColor = ARGB( 0xffff00ff );
						result = TAG_STYLE;
						continue;
					}

				case 'u':
					// Should be form like	#u	, underline.
					{
						style.m_underline = !style.m_underline;
						result = TAG_STYLE;
						continue;
					}

				case 'b':
					// Should be form like	#b	, bold.
					{
						const WString& face = style.m_font->GetName();
						if( style.m_font->GetWeight() == 400 )
							style.m_font = StyleFont::CreateFont( face , style.m_font->GetLogicHeight() , 700 , style.m_font->GetItalic() , style.m_font->GetAntialias() );
						else
							style.m_font = StyleFont::CreateFont( face , style.m_font->GetLogicHeight() , 400 , style.m_font->GetItalic() , style.m_font->GetAntialias() );
						result = TAG_STYLE;
						continue;
					}

				case 'i':
					// Should be form like	#i	, italic.
					{
						const WString& face = style.m_font->GetName();
						if( style.m_font->GetItalic() )
							style.m_font = StyleFont::CreateFont( face , style.m_font->GetLogicHeight() , style.m_font->GetWeight() , false , style.m_font->GetAntialias() );
						else
							style.m_font = StyleFont::CreateFont( face , style.m_font->GetLogicHeight() , style.m_font->GetWeight() , true , style.m_font->GetAntialias() );
						result = TAG_STYLE;
						continue;
					}

				case '^':
					// Should be form like	#^2 , change font size.
					{
						int fs = 0;
						wchar_t ch = buffer[0];
						++buffer;
						switch( ch )
						{
						case '1': fs = 11; break;
						case '2': fs = 12; break;
						case '3': fs = 14; break;
						case '4': fs = 16; break;
						case '5': fs = 20; break;
						case '6': fs = 24; break;
						default:
							TRACE_WARNING( _S("XTagLayout::ParseTag: '#^' tag has unexpected size followed.") );
							continue;
						}

						const WString& face = style.m_font->GetName();
						style.m_font = StyleFont::CreateFont( face , fs , style.m_font->GetLogicHeight() , style.m_font->GetItalic() , style.m_font->GetAntialias() );
						result = TAG_STYLE;
						continue;
					}

				case 'f':
					// Should be form like #f{Tahoma} , font tag.
					{
						if( *buffer != '{' )
						{
							TRACE_WARNING( _S("XTagLayout::ParseTag: '#f' tag corrupted with non brick header.") );
							return TAG_NONE;
						}
						++buffer;

						WString face;
						while( *buffer )
						{
							if( *buffer == '}' )
							{
								++buffer;
								break;
							}

							if( *buffer == '\0' )
							{
								TRACE_WARNING( _S("XTagLayout::ParseTag: '#f' tag corrupted with unexpected end of text.") );
								return TAG_NONE;
							}

							if( ( *buffer == '\n' ) || ( *buffer == '\r' ) )
							{
								TRACE_WARNING( _S("XTagLayout::ParseTag: '#f' tag corrupted with unexpected end of line.") );
								return TAG_NONE;
							}

							face += *buffer;
							++buffer;
						}

						style.m_font = StyleFont::CreateFont( face , style.m_font->GetLogicHeight() , style.m_font->GetWeight() , style.m_font->GetItalic() , style.m_font->GetAntialias() );
						result = TAG_STYLE;
						continue;
					}

				case 's':
					// Should be form like	#s<Title> , using global style.
					{
						break;
					}

				case '<':
					// Should be form like	#<pict.png> , an image tag.
					{
						WString path;
						while( *buffer )
						{
							if( *buffer == '>' )
							{
								++buffer;
								break;
							}

							if( *buffer == '\0' )
							{
								TRACE_WARNING( _S("XTagLayout::ParseTag: '#<' tag corrupted with unexpected end of text.") );
								return TAG_NONE;
							}

							if( ( *buffer == '\n' ) || ( *buffer == '\r' ) )
							{
								TRACE_WARNING( _S("XTagLayout::ParseTag: '#<' tag corrupted with unexpected end of line.") );
								return TAG_NONE;
							}

							path += *buffer;
							++buffer;
						}

						// Make image.

						if( m_imageRes == NULL )
						{
							TRACE_ERROR( _S("XTagLyaout::ParseTag: Image resource loader is not set for control.") );
							return TAG_NONE;
						}

						Ref<IBitmap> icon = m_imageRes->LoadImage( path );
						if( icon.IsNull() )
							return TAG_NONE;

						tag = AllocTag();
						tag->SetIcon( icon );
						tag->m_start = pos.x;
						return TAG_IMAGE;
					}

				case '0':
					// Should be form like	#100 , an image tag of emotes.
					{
						return TAG_IMAGE;
					}
				}
			}
			return result;
		}
	}
	// AppendLine
	void XTagLayout::AppendLine( const Line& line , Pair<int>& cursor )
	{
		XS_ASSERT( !line.IsEmpty() );
		m_lines.push_back( line );
		Line& l = (*m_lines.rbegin());
		l.CompleteLine();

		Rect<int> placement = GetBound();
		int x = m_rect.left() - placement.left();
		int y = m_rect.top() - placement.top();

		if( m_lines.size() == 1 )
		{
			l.m_base = y;
		}
		else
		{
			Line& prev = m_lines[ m_lines.size() - 2 ];
			l.m_base = prev.m_base + prev.m_height;
		}

		cursor.y += line.GetHeight();
		cursor.x = x;
	}
	// SetImageResource
	void XTagLayout::SetImageResource( ImageResource * imageRes )
	{
		m_imageRes = imageRes;
	}
	// CreatedTag
	void XTagLayout::CreatedTag( Tag * created )
	{
		if( created->IsTickable() )
		{
			m_tickTag.insert( created );
			if( m_tickTag.size() == 1 && m_parent.IsValid() )
				m_parent->AddTickable( this );
		}
	}
	// ReleaseTag
	void XTagLayout::ReleaseTag( Tag * tag )
	{
		if( tag->IsTickable() )
		{
			m_tickTag.erase( tag );
			if( m_tickTag.empty() && m_parent.IsValid() )
				m_parent->DelTickable( this );
		}
		tag->Clear();
	}
	// GetLayoutHeight
	size_t XTagLayout::GetLayoutHeight( void ) const
	{
		if( m_lines.size() == 0 )
			return 0;

		const Line& line = m_lines.back();
		return line.m_base + line.m_height;
	}
	// GetLineCount
	size_t XTagLayout::GetLineCount( void ) const
	{
		return m_lines.size();
	}
	// GetLineHeight
	size_t XTagLayout::GetLineHeight( size_t line ) const
	{
		XS_ASSERT( line < m_lines.size() );
		return m_lines[line].m_height;
	}
	// GetLineWidth
	size_t XTagLayout::GetLineWidth( size_t line ) const
	{
		XS_ASSERT( line < m_lines.size() );
		return m_lines[line].m_width;
	}
} // namespace xeres
